namespace SolidMvvm {
	using System;
	using System.Globalization;
	using System.Windows;
	using System.Windows.Data;

	/// <summary>
	///     This converter requires a ConverterParameter of one of the VisibilityConverterEnum values.
	/// </summary>
	[ValueConversion(typeof(object), typeof(Visibility))]
	public class VisibilityConverter : IValueConverter {
		public enum VisibilityConverterEnum {
			FalseToCollapsedAndNotFalseToVisible,

			FalseToHiddenAndNotFalseToVisible,

			FalseToVisibleAndNotFalseToCollapsed,

			FalseToVisibleAndNotFalseToHidden,

			GreaterThanZeroToCollapsedAndLessThanOrEqualToZeroToVisible,

			GreaterThanZeroToHiddenAndLessThanOrEqualToZeroToVisible,

			GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToCollapsed,

			GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToHidden,

			// These are useful for "SelectedIndex == -1" scenarios, such as for TabControl or ListBox.
			NegativeOneToCollapsedAndNotNegativeOneToVisible,

			NegativeOneToHiddenAndNotNegativeOneToVisible,

			NegativeOneToVisibleAndNotNegativeOneToCollapsed,

			NegativeOneToVisibleAndNotNegativeOneToHidden,

			NullToCollapsedAndNotNullToVisible,

			NullToHiddenAndNotNullToVisible,

			NullToVisibleAndNotNullToCollapsed,

			NullToVisibleAndNotNullToHidden,

			TrueToCollapsedAndFalseToHidden,

			TrueToCollapsedAndFalseToVisible,

			TrueToCollapsedAndNotTrueToHidden,

			TrueToCollapsedAndNotTrueToVisible,

			TrueToHiddenAndFalseToCollapsed,

			TrueToHiddenAndFalseToVisible,

			TrueToHiddenAndNotTrueToCollapsed,

			TrueToHiddenAndNotTrueToVisible,

			TrueToVisibleAndNotTrueToCollapsed,

			TrueToVisibleAndNotTrueToHidden,

			TrueToVisibleAndFalseToCollapsed,

			TrueToVisibleAndFalseToHidden
		}

		public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
			if (parameter == null)
				throw new ArgumentNullException("A converter parameter is required.");

			VisibilityConverterEnum visibilityConverter;
			if (!Enum.TryParse((string)parameter, out visibilityConverter))
				throw new ArgumentException("Unexpected converter parameter.");

			switch (visibilityConverter) {
				case VisibilityConverterEnum.FalseToCollapsedAndNotFalseToVisible:
					return (bool?)value == false ? Visibility.Collapsed : Visibility.Visible;
				case VisibilityConverterEnum.FalseToHiddenAndNotFalseToVisible:
					return (bool?)value == false ? Visibility.Hidden : Visibility.Visible;
				case VisibilityConverterEnum.FalseToVisibleAndNotFalseToCollapsed:
					return (bool?)value == false ? Visibility.Visible : Visibility.Collapsed;
				case VisibilityConverterEnum.FalseToVisibleAndNotFalseToHidden:
					return (bool?)value == false ? Visibility.Visible : Visibility.Hidden;

				case VisibilityConverterEnum.GreaterThanZeroToCollapsedAndLessThanOrEqualToZeroToVisible:
					return GetNumericValueAsDouble(value) > 0 ? Visibility.Collapsed : Visibility.Visible;
				case VisibilityConverterEnum.GreaterThanZeroToHiddenAndLessThanOrEqualToZeroToVisible:
					return GetNumericValueAsDouble(value) > 0 ? Visibility.Hidden : Visibility.Visible;
				case VisibilityConverterEnum.GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToCollapsed:
					return GetNumericValueAsDouble(value) > 0 ? Visibility.Visible : Visibility.Collapsed;
				case VisibilityConverterEnum.GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToHidden:
					return GetNumericValueAsDouble(value) > 0 ? Visibility.Visible : Visibility.Hidden;

				case VisibilityConverterEnum.NegativeOneToCollapsedAndNotNegativeOneToVisible:
					return Math.Abs(GetNumericValueAsDouble(value) + 1) < double.Epsilon ? Visibility.Collapsed : Visibility.Visible;
				case VisibilityConverterEnum.NegativeOneToHiddenAndNotNegativeOneToVisible:
					return Math.Abs(GetNumericValueAsDouble(value) + 1) < double.Epsilon ? Visibility.Hidden : Visibility.Visible;
				case VisibilityConverterEnum.NegativeOneToVisibleAndNotNegativeOneToCollapsed:
					return Math.Abs(GetNumericValueAsDouble(value) + 1) < double.Epsilon ? Visibility.Visible : Visibility.Collapsed;
				case VisibilityConverterEnum.NegativeOneToVisibleAndNotNegativeOneToHidden:
					return Math.Abs(GetNumericValueAsDouble(value) + 1) < double.Epsilon ? Visibility.Visible : Visibility.Hidden;

				case VisibilityConverterEnum.NullToCollapsedAndNotNullToVisible:
					return value == null ? Visibility.Collapsed : Visibility.Visible;
				case VisibilityConverterEnum.NullToHiddenAndNotNullToVisible:
					return value == null ? Visibility.Hidden : Visibility.Visible;
				case VisibilityConverterEnum.NullToVisibleAndNotNullToCollapsed:
					return value == null ? Visibility.Visible : Visibility.Collapsed;
				case VisibilityConverterEnum.NullToVisibleAndNotNullToHidden:
					return value == null ? Visibility.Visible : Visibility.Hidden;

				case VisibilityConverterEnum.TrueToCollapsedAndNotTrueToVisible:
				case VisibilityConverterEnum.TrueToCollapsedAndFalseToVisible:
					return (bool?)value == true ? Visibility.Collapsed : Visibility.Visible;
				case VisibilityConverterEnum.TrueToHiddenAndNotTrueToVisible:
				case VisibilityConverterEnum.TrueToHiddenAndFalseToVisible:
					return (bool?)value == true ? Visibility.Hidden : Visibility.Visible;
				case VisibilityConverterEnum.TrueToVisibleAndNotTrueToCollapsed:
				case VisibilityConverterEnum.TrueToVisibleAndFalseToCollapsed:
					return (bool?)value == true ? Visibility.Visible : Visibility.Collapsed;
				case VisibilityConverterEnum.TrueToVisibleAndNotTrueToHidden:
				case VisibilityConverterEnum.TrueToVisibleAndFalseToHidden:
					return (bool?)value == true ? Visibility.Visible : Visibility.Hidden;

				case VisibilityConverterEnum.TrueToHiddenAndFalseToCollapsed:
				case VisibilityConverterEnum.TrueToHiddenAndNotTrueToCollapsed:
					return (bool?)value == true ? Visibility.Hidden : Visibility.Collapsed;
				case VisibilityConverterEnum.TrueToCollapsedAndFalseToHidden:
				case VisibilityConverterEnum.TrueToCollapsedAndNotTrueToHidden:
					return (bool?)value == true ? Visibility.Collapsed : Visibility.Hidden;

				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		/// <summary>
		///     For most of these, we can't do a back conversion, but some of them are logical. For example, we can't know whether
		///     the opposite of true in a bool? type is false or null, so we throw NotImplementedException.
		/// </summary>
		/// <param name="value"> </param>
		/// <param name="targetType"> </param>
		/// <param name="parameter"> </param>
		/// <param name="culture"> </param>
		/// <returns> </returns>
		public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
			if (parameter == null)
				throw new ArgumentNullException("Unexpected null converter parameter.");

			VisibilityConverterEnum visibilityConverter;
			if (!Enum.TryParse((string)parameter, out visibilityConverter))
				throw new ArgumentException("Unexpected converter parameter.");

			switch (visibilityConverter) {
				case VisibilityConverterEnum.FalseToCollapsedAndNotFalseToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.FalseToHiddenAndNotFalseToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.FalseToVisibleAndNotFalseToCollapsed:
					throw new NotImplementedException();
				case VisibilityConverterEnum.FalseToVisibleAndNotFalseToHidden:
					throw new NotImplementedException();

				case VisibilityConverterEnum.GreaterThanZeroToCollapsedAndLessThanOrEqualToZeroToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.GreaterThanZeroToHiddenAndLessThanOrEqualToZeroToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToCollapsed:
					throw new NotImplementedException();
				case VisibilityConverterEnum.GreaterThanZeroToVisibleAndLessThanOrEqualToZeroToHidden:
					throw new NotImplementedException();

				case VisibilityConverterEnum.NullToCollapsedAndNotNullToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.NullToHiddenAndNotNullToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.NullToVisibleAndNotNullToCollapsed:
					throw new NotImplementedException();
				case VisibilityConverterEnum.NullToVisibleAndNotNullToHidden:
					throw new NotImplementedException();

				case VisibilityConverterEnum.TrueToCollapsedAndNotTrueToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.TrueToHiddenAndNotTrueToVisible:
					throw new NotImplementedException();
				case VisibilityConverterEnum.TrueToVisibleAndNotTrueToCollapsed:
					throw new NotImplementedException();
				case VisibilityConverterEnum.TrueToVisibleAndNotTrueToHidden:
					throw new NotImplementedException();

				case VisibilityConverterEnum.TrueToHiddenAndNotTrueToCollapsed:
					throw new NotImplementedException();
				case VisibilityConverterEnum.TrueToCollapsedAndNotTrueToHidden:
					throw new NotImplementedException();

				case VisibilityConverterEnum.TrueToCollapsedAndFalseToVisible:
					return (Visibility)value == Visibility.Collapsed;
				case VisibilityConverterEnum.TrueToHiddenAndFalseToVisible:
					return (Visibility)value == Visibility.Hidden;
				case VisibilityConverterEnum.TrueToVisibleAndFalseToCollapsed:
					return (Visibility)value == Visibility.Visible;
				case VisibilityConverterEnum.TrueToVisibleAndFalseToHidden:
					return (Visibility)value == Visibility.Visible;
				case VisibilityConverterEnum.TrueToCollapsedAndFalseToHidden:
					return (Visibility)value == Visibility.Collapsed;
				case VisibilityConverterEnum.TrueToHiddenAndFalseToCollapsed:
					return (Visibility)value == Visibility.Hidden;

				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		static double GetNumericValueAsDouble(object value) {
			if (value is int)
				return (int)value;
			if (value is double)
				return (double)value;
			if (value is float)
				return (float)value;

			throw new ArgumentException("Unexpected numeric type.");
		}
	}
}
